#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#define NUM 1024
#define SIZE 64 
#define SEP " " 
// #define debug 1


#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppendRedir 3

int redir = NoneRedir;
char* filename = NULL;

char cwd[1024];
char myenv[128][1024];
int cnt = 0;
int lastcode = 0; // 记录退出码

char* getHomename()
{
    char* homename = getenv("HOME");
    if(homename)
        return homename;
    else
        return (char*)"none"; 
}

const char* getUsername()
{
    char* username = getenv("USER");
    if(username)
        return username;
    else
        return "none"; 
}

const char* getHostname()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)
        return hostname;
    else
        return "none"; 
}


const char* getCwd()
{
    char* cwd = getenv("PWD");
    if(cwd)
        return cwd;
    else
        return "none"; 
}

int getUserCommand(char* command , int num)
{
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    
    // scanf("%s",usercommand); scanf读到空格就会自动停止，所以这里不使用scanf
    char* r = fgets(command,num,stdin);
    if(r == NULL) return -1;  // 读取失败返回-1
    // 读取字符串时，会自动获取到'\n'，我们需要将这个'\n'去掉
    command[strlen(command)-1] = '\0';
    // 由于读入字符串时，必定会有一个\n所以这么不可能会越界

    // 用于测试输入的字符串是否符合我们的预期   
    // printf("%s\n",command);    

    // 当命令行只输入回车时，则没有必要创建子进程来执行任务
    if(strlen(command) == 0)
        return 0;

    return 1;
}

void SplitCommand(char* in , char* out[])    
{    
    int argc = 0;    
    out[argc++] = strtok(in,SEP);    
    while(out[argc++] = strtok(NULL,SEP));    
    // strtok函数分割失败时会返回NULL，正好是我们字符串数组结尾所需要的
    // 所以分割的方式可以使用赋值作为循环判断条件进行循序
    // 若是大家不习惯可以使用下面这一种分割方式
    

    //while(1)    
    //{    
    //    out[argc] = strtok(NULL,SEP);    
    //    if(out[argc] == NULL)                                                                                                              
    //        break;    
    //    argc++;                                                                                                                            
    //}                                                                                                                                      
#ifdef debug 
    for(int i = 0 ; out[i] ; i++)                                                                                                          
    {                                                                                                                                      
        printf("%d : %s \n",i,out[i]);                                                                                                     
    }        
#endif
}                                                                                                                                          
                                                                                                                                           
int execute(char* argv[])
{
    pid_t id = fork();

    if(id < 0) return -1;
    else if(id == 0)
    {
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename,O_RDONLY);
            dup2(fd,0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        else 
        {
            // do nothing
        }

        execvp(argv[0],argv);
        // 若替换失败则子进程退出
        exit(-1);
    }
    else 
    {
        // sleep(1);
        int status = 0;
        pid_t rid = wait(&status);
        if(rid>0)
        {
            lastcode = WEXITSTATUS(status);
        }
        
    }
    return 0;
}

void cd(char* path)
{
    chdir(path);
    char tmp[1024];
    // char* tmp = getenv("PWD");
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}

// 内键命令并执行1，非内键命令0
int dobuildin(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path = NULL;
        if(argv[1] == NULL)
            path = getHomename();
        else 
            path = argv[1];

        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL)
            return 1;
        else 
        {
            strcpy(myenv[cnt],argv[1]);
            putenv(myenv[cnt++]);
            return 1;
        }
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[1] == NULL)
        {
            printf("\n");
            return 1;
        }
        else if(*(argv[1]) == '$' && strlen(argv[1]) >= 2)
        {
            // 输出退出码
            if(*(argv[1]+1) == '?')
            {
                printf("%d\n",lastcode);
                lastcode = 0;
            }
            else  // 输出环境变量  
            {
                const char* enval = getenv(argv[1]+1);
                if(enval)
                {
                    printf("%s",enval);
                }
                else 
                {
                    printf("\n");
                }
            }
        }
        else 
        {
            printf("%s",argv[1]);
        }
        return 1;
    }
    else if(0){}
    return 0;
}


#define SkipSpace(pos) do{while(isspace(*pos)) pos++;}while(0)

void CheckRedir(char command[])
{
    char* start = command;
    char* end = command +  strlen(command);

    while(start < end)
    {
        // 输入重定向
        if(*end == '<')
        {
            *end = '\0';
            filename = end + 1;
            SkipSpace(filename);
            redir = InputRedir;
        }
        else if(*end == '>')
        {
            // 追加重定向
            if(*(end-1) == '>')
            {
                *(end-1) = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = AppendRedir;
            }
            // 输出重定向
            else 
            {
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = OutputRedir;
            }
        }
        end--;
    }
}

int main()                                                                                                                                 
{                                                                                                                                           
    while(1)                                                                                                                               
    {                                                                                                                                      
        // 初始默认为非重定向
        redir = NoneRedir;
        filename = NULL;

        char usercommand[NUM];                                                                                                             
        char* argv[SIZE];    
        // 打印命令提示符和获取命令行命令字符串    
        int x = getUserCommand(usercommand,NUM);    
    
        if(x <= 0) continue;    
    
        // 判断是否为重定向
        CheckRedir(usercommand);

        // 分割命令行  "ls -a -l" -> "ls" "-a" "-l"
        SplitCommand(usercommand,argv); 

        // 判断是否为內键命令
        x = dobuildin(argv);
        if(x == 1)
            continue;

        // 执行指令
        execute(argv);
    }
    return 0;
}



